/*
* FaceCat图形通讯框架(非开源)
* 著作权编号:2015SR229355+2020SR0266727
* 上海卷卷猫信息技术有限公司
*/

package facecat.topin.sock;

import facecat.topin.core.FCTran;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.NetworkChannel; 

/*
* 服务端套接字连接
*/
public class FCServerSocket {
    class SocketHandler implements Runnable {
        @Override
        public void run() {
            readData2(m_socket);
        }
        public Socket m_socket;
    }
    
    private HashMap<Integer, SocketData> m_datas = new HashMap<Integer, SocketData>();
    public int m_hSocket;
    private Selector m_selector = null;
    private ServerSocket m_socket = null;
    private ServerSocketChannel m_ssc = null;
    private ExecutorService m_service;
    
    /// <summary>
    /// 模式
    /// </summary>
    public static int m_mode = 0;

    public int close() {
        if (m_socket != null) {
            try {
                m_socket.close();
                m_socket = null;
                return 1;
            } catch (Exception ex) {

            }
        }
        if(m_service != null){
            try{
                m_service.shutdown();
                m_service.awaitTermination(10, TimeUnit.SECONDS);
                m_service = null;
            }catch(Exception ex){
                
            }
        }
        return -1;
    }

    public int closeClient(int socketID) {
        if (m_datas.containsKey(socketID)) {
            try {
                m_datas.get(socketID).m_socket.close();
                return 1;
            } catch (Exception ex) {

            }
        }
        return -1;
    }

    public int recv(SocketData data) {
        if (!data.m_submit) {
            if (data.m_len >= 4 && data.m_buffer[0] == 109 && data.m_buffer[1] == 105 && data.m_buffer[2] == 97 && data.m_buffer[3] == 111) {
                data.m_submit = true;
                if(data.m_len == 4){
                    return 1;
                }else{
                    int newLen = data.m_len - 4;
                    byte[] buffer = new byte[data.m_len - 4];
                    System.arraycopy(data.m_buffer, 4, buffer, 0, newLen);
                    data.m_buffer = buffer;
                    data.m_len = newLen;
                }
            } else {
                return -1;
            }
        }
        int intSize = 4;
        data.m_index = 0;
        while (data.m_index < data.m_len) {
            int diffSize = 0;
            if (!data.m_get) {
                diffSize = intSize - data.m_headSize;
                if (diffSize == 0) {
                    data.m_head = (0xff & data.m_buffer[data.m_index]) 
                            | (0xff00 & (data.m_buffer[data.m_index + 1] << 8))
                            | (0xff0000 & (data.m_buffer[data.m_index + 2] << 16))
                            | (0xff000000 & (data.m_buffer[data.m_index + 3] << 24));
                } else {
                    for (int i = 0; i < diffSize; i++) {
                        data.m_headStr[data.m_headSize + i] = data.m_buffer[i];
                    }
                    data.m_head = (0xff & data.m_headStr[0]) 
                            | (0xff00 & (data.m_headStr[1] << 8))
                            | (0xff0000 & (data.m_headStr[2] << 16)) 
                            | (0xff000000 & (data.m_headStr[3] << 24));
                }
                if (data.m_str != null) {
                    data.m_str = null;
                }
                data.m_str = new byte[data.m_head];
                if (diffSize > 0) {
                    for (int i = 0; i < data.m_headSize; i++) {
                        data.m_str[i] = data.m_headStr[i];
                    }
                    data.m_pos += data.m_headSize;
                    data.m_headSize = intSize;
                }
            }
            data.m_bufferRemain = data.m_len - data.m_index;
            data.m_strRemain = data.m_head - data.m_pos;
            data.m_get = data.m_strRemain > data.m_bufferRemain;
            int remain = Math.min(data.m_strRemain, data.m_bufferRemain);
            System.arraycopy(data.m_buffer, data.m_index, data.m_str, data.m_pos, remain);
            data.m_pos += remain;
            data.m_index += remain;
            if (!data.m_get) {
                FCServerSockets.recvServerMsg(data.m_hSocket, m_hSocket, data.m_str, data.m_head);
                data.m_head = 0;
                data.m_pos = 0;
                data.m_readHead = 2;
                if (data.m_len - data.m_index == 0 || data.m_len - data.m_index >= intSize) {
                    data.m_headSize = intSize;
                } else {
                    data.m_headSize = data.m_bufferRemain - data.m_strRemain;
                    for (int j = 0; j < data.m_headSize; j++) {
                        data.m_headStr[j] = data.m_buffer[data.m_index + j];
                    }
                    break;
                }
            }
        }
        return 1;
    }
    
    public void readData2(Socket socket){
        int socketID = socket.hashCode();
        SocketData data = new SocketData();
        data.m_socket = socket;
        m_datas.put(socketID, data);
        data.m_hSocket = socketID;
        DataInputStream sReader = null;
         try{
            sReader = new DataInputStream(socket.getInputStream());
             while(true){
                 if (!data.m_submit)
                {
                    byte[] buffer = new byte[4];
                    int copyLen = sReader.read(buffer);
                    data.m_buffer = buffer;
                    data.m_len = copyLen;
                    if (data.m_len == 4 && data.m_buffer[0] == 109 && data.m_buffer[1] == 105 && data.m_buffer[2] == 97 && data.m_buffer[3] == 111) {
                        data.m_submit = true;
                        continue;
                    }else{
                        return;
                    }
                }else{
                     byte[] buffer = new byte[1024];
                        int copyLen = sReader.read(buffer);
                        if (copyLen == 0 || copyLen == -1)
                        {
                            break;
                        }
                        data.m_buffer = buffer;
                        data.m_len = copyLen;
                        int intSize = 4;
                        data.m_index = 0;
                        while (data.m_index < data.m_len)
                        {
                            int diffSize = 0;
                            if (!data.m_get)
                            {
                                diffSize = intSize - data.m_headSize;
                                if (diffSize == 0) {
                                    data.m_head = (0xff & buffer[data.m_index]) | (0xff00 & (buffer[data.m_index + 1] << 8))
                                            | (0xff0000 & (buffer[data.m_index + 2] << 16)) | (0xff000000 & (buffer[data.m_index + 3] << 24));
                                } else {
                                    for (int i = 0; i < diffSize; i++) {
                                        data.m_headStr[data.m_headSize + i] = buffer[i];
                                    }
                                    data.m_head = (0xff & data.m_headStr[0]) | (0xff00 & (data.m_headStr[1] << 8))
                                            | (0xff0000 & (data.m_headStr[2] << 16)) | (0xff000000 & (data.m_headStr[3] << 24));
                                }
                                if (data.m_str != null)
                                {
                                    data.m_str = null;
                                }
                                data.m_str = new byte[data.m_head];
                                if (diffSize > 0)
                                {
                                    for (int i = 0; i < data.m_headSize; i++)
                                    {
                                        data.m_str[i] = data.m_headStr[i];
                                    }
                                    data.m_pos += data.m_headSize;
                                    data.m_headSize = intSize;
                                }
                            }
                            data.m_bufferRemain = data.m_len - data.m_index;
                            data.m_strRemain = data.m_head - data.m_pos;
                            data.m_get = data.m_strRemain > data.m_bufferRemain;
                            int remain = Math.min(data.m_strRemain, data.m_bufferRemain);
                            System.arraycopy(data.m_buffer, data.m_index, data.m_str, data.m_pos, remain);
                            data.m_pos += remain;
                            data.m_index += remain;
                            if (!data.m_get)
                            {
                                FCServerSockets.recvServerMsg(data.m_hSocket, m_hSocket, data.m_str, data.m_head);
                                data.m_head = 0;
                                data.m_pos = 0;
                                if (data.m_len - data.m_index == 0 || data.m_len - data.m_index >= intSize)
                                {
                                    data.m_headSize = intSize;
                                }
                                else
                                {
                                    data.m_headSize = data.m_bufferRemain - data.m_strRemain;
                                    for (int j = 0; j < data.m_headSize; j++)
                                    {
                                        data.m_headStr[j] = data.m_buffer[data.m_index + j];
                                    }
                                }
                                if(m_mode == 1){
                                    return;
                                }
                            }
                        }
                 }
             }
        }catch(Exception ex){
        }
         finally{
              try {
                socket.close();
                if(sReader != null){
                    sReader.close();
                }
                FCServerSockets.writeServerLog(socketID, m_hSocket, 2, "socket exit");
            } catch (Exception ex) {

            }
         }
    }

    public void readData(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        Socket socket = socketChannel.socket();
        ByteBuffer receiveBuffer = null;
        int len = -1;
        try {
            int socketID = socket.hashCode();
            SocketData data = m_datas.get(socketID);
            receiveBuffer = ByteBuffer.allocate(data.m_nextSize);
            len = socketChannel.read(receiveBuffer);
            if(len < 0){
                socketChannel.close();
                FCServerSockets.writeServerLog(socketID, m_hSocket, 2, "socket exit");
                return;
            }
            if(len > 0){
                byte[] buffer = receiveBuffer.array();
                if (data != null) {
                    data.m_buffer = buffer;
                    data.m_len = len;
                    if (data.m_submit)
                    {
                        recv(data);
                        if (data.m_readHead == 0)
                        {
                            data.m_readHead = 1;
                            int headSize2 = (0xff & buffer[0]) | (0xff00 & (buffer[1] << 8))
                                                | (0xff0000 & (buffer[2] << 16)) | (0xff000000 & (buffer[3] << 24));
                            int nextSize = (headSize2 < 1024 ? headSize2 : 1024);
                            data.m_nextSize = nextSize;
                        }
                        else
                        {
                            if (data.m_readHead == 1)
                            {
                                int dataRemain = data.m_head - data.m_pos;
                                int nextSize = (dataRemain < 1024 ? dataRemain : 1024);
                                data.m_nextSize = nextSize;
                            }
                            else if (data.m_readHead == 2)
                            {
                                data.m_readHead = 0;
                                data.m_nextSize = 4;
                            }
                        }
                    }else{
                        int state = recv(data);
                        if (state == -1) {
                            socketChannel.close();
                            FCServerSockets.writeServerLog(socketID, m_hSocket, 2, "socket exit");
                            m_datas.put(socketID, null);
                        }
                    }
                }
            }
        } catch (Exception ex) {
             int socketID = socket.hashCode();
            socketChannel.close();
            FCServerSockets.writeServerLog(socketID, m_hSocket, 2, "socket exit");
        }
        if(receiveBuffer != null){
            receiveBuffer.clear();
        }
    }

    public int send(int socketID, byte[] str, int len) {
        int ret = -1;
        synchronized(this){
            try {
                SocketData data = m_datas.get(socketID);
                if (data != null) {
                    if(m_mode == 0){
                        SocketChannel channel = data.m_socket.getChannel();
                        ByteBuffer sendBuffer = ByteBuffer.wrap(str);
                        while(sendBuffer.hasRemaining()) {
                            channel.write(sendBuffer);
                        }
                        sendBuffer.clear();
                    }else{
                        data.m_socket.getOutputStream().write(str);
                        data.m_socket.getOutputStream().flush();
                    }
                    ret = len;
                }
            } catch (Exception ex) {
                ret = -1;
            }
        }
        return ret;
    }

    public int startTCP(int port) throws IOException {
        try {
            if(m_mode == 0){ 
                int number = 1;
                m_ssc = ServerSocketChannel.open();
                m_ssc.configureBlocking(false);
                m_socket = m_ssc.socket();
                m_selector = Selector.open();
                m_socket.bind(new InetSocketAddress(port));
                m_ssc.register(m_selector, SelectionKey.OP_ACCEPT);
                for (int i = 0; i < number; i++) {
                    Thread thread = new Thread() {
                        public void run() {
                            while (true) {
                                try {
                                    if (m_selector.select() == 0) {
                                        continue;
                                    }
                                    Iterator<SelectionKey> iter = m_selector.selectedKeys().iterator();
                                    while (iter.hasNext()) {
                                        SelectionKey key = iter.next();
                                        iter.remove();
                                        if (key.isAcceptable()) {
                                            SocketChannel channel = m_ssc.accept();
                                            String ip = channel.socket().getInetAddress().getHostAddress();
                                            boolean limit = false;
                                            if(m_limitTimes > 0){
                                                Calendar calendar = Calendar.getInstance();
                                                double time = FCTran.dateToNum(calendar);
                                                synchronized(m_ipCaches){
                                                    if(time - m_lastClearTime > m_clearInterval){
                                                        m_lastClearTime = time;
                                                        m_ipCaches.clear();
                                                    }
                                                    int count = 0;
                                                    if(m_ipCaches.containsKey(ip)){
                                                        count = m_ipCaches.get(ip) + 1;
                                                    }else{
                                                        count = 1;
                                                    }
                                                    m_ipCaches.put(ip, count);
                                                    if(count > m_limitTimes){
                                                        limit = true;
                                                    }
                                                }
                                            }
                                           synchronized(m_blackIPs){
                                                if(m_blackIPs.containsKey(ip)){
                                                    limit = true; 
                                                }
                                            }
                                           if(limit){
                                               channel.socket().close();
                                           }else{
                                                channel.configureBlocking(false);
                                                channel.register(m_selector, SelectionKey.OP_READ);
                                                //channel.setOption(StandardSocketOptions.SO_SNDBUF, 1024);
                                                SocketData data = new SocketData();
                                                data.m_socket = channel.socket();
                                                data.m_hSocket = data.m_socket.hashCode();
                                                m_datas.put(data.m_hSocket, data);
                                                FCServerSockets.writeServerLog(data.m_hSocket, m_hSocket, 1, "accept:" + channel.getRemoteAddress());
                                           }
                                        } 
                                        if (key.isReadable()) {
                                            readData(key);
                                        }
                                    }
                                } catch (Exception ex) {

                                }
                            }
                        }
                    };
                    thread.start();
                }

                
                /*m_socket = new ServerSocket(port);
                m_service = Executors.newCachedThreadPool();
                Thread thread = new Thread() {
                        public void run() {
                            while (m_socket != null) {
                                try{
                                    Socket socket = m_socket.accept();
                                    String ip = socket.getInetAddress().getHostAddress();
                                    boolean limit = false;
                                    if(m_limitTimes > 0){
                                        Calendar calendar = Calendar.getInstance();
                                        double time = FCTran.dateToNum(calendar);
                                        synchronized(m_ipCaches){
                                            if(time - m_lastClearTime > m_clearInterval){
                                                m_lastClearTime = time;
                                                m_ipCaches.clear();
                                            }
                                            int count = 0;
                                            if(m_ipCaches.containsKey(ip)){
                                                count = m_ipCaches.get(ip) + 1;
                                            }else{
                                                count = 1;
                                            }
                                            m_ipCaches.put(ip, count);
                                            if(count > m_limitTimes){
                                                limit = true;
                                            }
                                        }
                                    }
                                   synchronized(m_blackIPs){
                                        if(m_blackIPs.containsKey(ip)){
                                            limit = true; 
                                        }
                                    }
                                    if(limit){
                                        socket.close();
                                    }else{
                                        FCServerSockets.writeServerLog(socket.hashCode(), m_hSocket, 1, "accept:" + ip + ":" + FCTran.intToStr(socket.getPort()));
                                        SocketHandler receiver = new SocketHandler();
                                        receiver.m_socket = socket;
                                        m_service.execute(receiver);
                                    }
                                }catch(Exception ex){
                                    
                                }
                            }
                        }
                        };
                thread.start();*/
            }else{
                m_socket = new ServerSocket(port);
                m_service = Executors.newCachedThreadPool();
                Thread thread = new Thread() {
                        public void run() {
                            while (m_socket != null) {
                                try{
                                    Socket socket = m_socket.accept();
                                    String ip = socket.getInetAddress().getHostAddress();
                                    boolean limit = false;
                                    if(m_limitTimes > 0){
                                        Calendar calendar = Calendar.getInstance();
                                        double time = FCTran.dateToNum(calendar);
                                        synchronized(m_ipCaches){
                                            if(time - m_lastClearTime > m_clearInterval){
                                                m_lastClearTime = time;
                                                m_ipCaches.clear();
                                            }
                                            int count = 0;
                                            if(m_ipCaches.containsKey(ip)){
                                                count = m_ipCaches.get(ip) + 1;
                                            }else{
                                                count = 1;
                                            }
                                            m_ipCaches.put(ip, count);
                                            if(count > m_limitTimes){
                                                limit = true;
                                            }
                                        }
                                    }
                                    synchronized(m_blackIPs){
                                        if(m_blackIPs.containsKey(ip)){
                                            limit = true;
                                        }
                                    }
                                    if(limit){
                                        socket.close();
                                    }else{
                                        SocketHandler receiver = new SocketHandler();
                                        receiver.m_socket = socket;
                                        m_service.execute(receiver);
                                    }
                                }catch(Exception ex){
                                    
                                }
                            }
                        }
                        };
                thread.start();
            }
            return 1;
        } catch (Exception ex) {

        }
        
        return -1;
    }
    
    /*
    * 清除缓存的间隔
    */
    public int m_clearInterval = -1;
    
    /*
     * 记录IP的缓存
     */
    public HashMap<String, Integer> m_ipCaches = new HashMap<String, Integer>();
    
    /*
     * 上次清理缓存的时间
     */
    public double m_lastClearTime;
    
    /*
     * 限制连接IP的次数
     */
    public int m_limitTimes = -1;
    
    /*
    * IP黑名单
    */
     public HashMap<String, Integer> m_blackIPs = new HashMap<String,Integer>();
        
     /*
     * 添加IP黑名单
     */
    public void addBlackIP(String ip)
    {
        synchronized (m_blackIPs)
        {
            m_blackIPs.put(ip, 0);
        }
    }
        
    /*
    * 移除IP黑名单
    */
    public void removeBlackIP(String ip)
    {
        synchronized (m_blackIPs)
        {
            if (m_blackIPs.containsKey(ip))
            {
                m_blackIPs.remove(ip);
            }
        }
    }
}
